#pragma once
#ifdef _WIN32
#ifndef ____TEMP_MOD_WIN_MINHOOK_HPP
#define ____TEMP_MOD_WIN_MINHOOK_HPP
#include <windows.h>
#include <TlHelp32.h>
#include "../env/env.hpp"
class __TEMP_MINHOOK
{
public:
	static void* mh_memset(void* pv, int     c, size_t  cb)
	{
		for (size_t i = 0; i < cb; i++)
		{
			((unsigned char*)pv)[i] = (unsigned char)c;
		}
		return pv;
	}
	static void* mh_memcpy(void* pvDest, const void* pvSrc, size_t cb)
	{
		for (size_t i = 0; i < cb; i++)
		{
			((unsigned char*)pvDest)[i] = ((const unsigned char*)pvSrc)[i];
		}
		return pvDest;
	}
public:
	typedef enum MH_STATUS
	{
		MH_UNKNOWN = -1,
		MH_OK = 0,
		MH_ERROR_ALREADY_INITIALIZED,
		MH_ERROR_NOT_INITIALIZED,
		MH_ERROR_ALREADY_CREATED,
		MH_ERROR_NOT_CREATED,
		MH_ERROR_ENABLED,
		MH_ERROR_DISABLED,
		MH_ERROR_NOT_EXECUTABLE,
		MH_ERROR_UNSUPPORTED_FUNCTION,
		MH_ERROR_MEMORY_ALLOC,
		MH_ERROR_MEMORY_PROTECT,
		MH_ERROR_MODULE_NOT_FOUND,
		MH_ERROR_FUNCTION_NOT_FOUND
	} MH_STATUS;
	typedef struct _HOOK_ENTRY
	{
		LPVOID pTarget;             // Address of the target function.
		LPVOID pDetour;             // Address of the detour or relay function.
		LPVOID pTrampoline;         // Address of the trampoline function.
		UINT8  backup[8];           // Original prologue of the target function.

		UINT8  patchAbove : 1;     // Uses the hot patch area.
		UINT8  isEnabled : 1;     // Enabled.
		UINT8  queueEnable : 1;     // Queued for enabling/disabling when != isEnabled.

		UINT   nIP : 4;             // Count of the instruction boundaries.
		UINT8  oldIPs[8];           // Instruction boundaries of the target function.
		UINT8  newIPs[8];           // Instruction boundaries of the trampoline function.
	} HOOK_ENTRY, * PHOOK_ENTRY;
	struct _HOOKS
	{
		PHOOK_ENTRY pItems;     // Data heap
		UINT        capacity;   // Size of allocated data heap, items
		UINT        size;       // Actual number of data items
	};
	typedef struct _FROZEN_THREADS
	{
		LPDWORD pItems;         // Data heap
		UINT    capacity;       // Size of allocated data heap, items
		UINT    size;           // Actual number of data items
	} FROZEN_THREADS, * PFROZEN_THREADS;
#if defined(_M_X64) || defined(__x86_64__)
	typedef struct _MEMORY_SLOT
	{
		union
		{
			struct _MEMORY_SLOT* pNext;
			UINT8 buffer[64];
		};
	} MEMORY_SLOT, * PMEMORY_SLOT;
#else
	typedef struct _MEMORY_SLOT
	{
		union
		{
			struct _MEMORY_SLOT* pNext;
			UINT8 buffer[32];
		};
	} MEMORY_SLOT, * PMEMORY_SLOT;
#endif
	typedef struct _MEMORY_BLOCK
	{
		struct _MEMORY_BLOCK* pNext;
		PMEMORY_SLOT pFree;         // First element of the free slot list.
		UINT usedCount;
	} MEMORY_BLOCK, * PMEMORY_BLOCK;
	typedef struct _TRAMPOLINE
	{
		LPVOID pTarget;         // [In] Address of the target function.
		LPVOID pDetour;         // [In] Address of the detour function.
		LPVOID pTrampoline;     // [In] Buffer address for the trampoline and relay function.

#if defined(_M_X64) || defined(__x86_64__)
		LPVOID pRelay;          // [Out] Address of the relay function.
#endif
		BOOL   patchAbove;      // [Out] Should use the hot patch area?
		UINT   nIP;             // [Out] Number of the instruction boundaries.
		UINT8  oldIPs[8];       // [Out] Instruction boundaries of the target function.
		UINT8  newIPs[8];       // [Out] Instruction boundaries of the trampoline function.
	} TRAMPOLINE, * PTRAMPOLINE;

#pragma pack(push, 1)
	typedef struct _JMP_REL_SHORT
	{
		UINT8  opcode;      // EB xx: JMP +2+xx
		UINT8  operand;
	} JMP_REL_SHORT, * PJMP_REL_SHORT;
	typedef struct _JMP_REL
	{
		UINT8  opcode;      // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx
		UINT32 operand;     // Relative destination address
	} JMP_REL, * PJMP_REL, CALL_REL;
	typedef struct _JMP_ABS
	{
		UINT8  opcode0;     // FF25 00000000: JMP [+6]
		UINT8  opcode1;
		UINT32 dummy;
		UINT64 address;     // Absolute destination address
	} JMP_ABS, * PJMP_ABS;
	typedef struct _CALL_ABS
	{
		UINT8  opcode0;     // FF15 00000002: CALL [+6]
		UINT8  opcode1;
		UINT32 dummy0;
		UINT8  dummy1;      // EB 08:         JMP +10
		UINT8  dummy2;
		UINT64 address;     // Absolute destination address
	} CALL_ABS;
	typedef struct _JCC_REL
	{
		UINT8  opcode0;     // 0F8* xxxxxxxx: J** +6+xxxxxxxx
		UINT8  opcode1;
		UINT32 operand;     // Relative destination address
	} JCC_REL;
	typedef struct _JCC_ABS
	{
		UINT8  opcode;      // 7* 0E:         J** +16
		UINT8  dummy0;
		UINT8  dummy1;      // FF25 00000000: JMP [+6]
		UINT8  dummy2;
		UINT32 dummy3;
		UINT64 address;     // Absolute destination address
	} JCC_ABS;

#if defined(_M_X64) || defined(__x86_64__)
#pragma pack(push,1)
	typedef struct {
		UINT8 len;
		UINT8 p_rep;
		UINT8 p_lock;
		UINT8 p_seg;
		UINT8 p_66;
		UINT8 p_67;
		UINT8 rex;
		UINT8 rex_w;
		UINT8 rex_r;
		UINT8 rex_x;
		UINT8 rex_b;
		UINT8 opcode;
		UINT8 opcode2;
		UINT8 modrm;
		UINT8 modrm_mod;
		UINT8 modrm_reg;
		UINT8 modrm_rm;
		UINT8 sib;
		UINT8 sib_scale;
		UINT8 sib_index;
		UINT8 sib_base;
		union {
			UINT8 imm8;
			UINT16 imm16;
			UINT32 imm32;
			UINT64 imm64;
		} imm;
		union {
			UINT8 disp8;
			UINT16 disp16;
			UINT32 disp32;
		} disp;
		UINT32 flags;
	} hde64s;
#pragma pack(pop)
	typedef hde64s HDE;
	static unsigned int hde_disasm(const void* code, HDE* hs)
	{
		unsigned char hde64_table[] = {
  0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5,
  0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1,
  0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea,
  0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0,
  0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab,
  0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92,
  0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90,
  0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b,
  0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,
  0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc,
  0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20,
  0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff,
  0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00,
  0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01,
  0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10,
  0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00,
  0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00,
  0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00,
  0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00,
  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
  0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00,
  0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40,
  0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43,
  0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
  0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40,
  0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06,
  0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07,
  0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04,
  0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10,
  0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00,
  0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb,
  0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff,
  0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09,
  0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff,
  0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08,
  0x00,0xf0,0x02,0x00
		};
		UINT8 x, c, * p = (UINT8*)code, cflags, opcode, pref = 0;
		UINT8* ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0;
		UINT8 op64 = 0;
		mh_memset((LPBYTE)hs, 0, sizeof(hde64s));
		for (x = 16; x; x--)
		{
			switch (c = *p++)
			{
			case 0xf3:
				hs->p_rep = c;
				pref |= 0x04;
				break;
			case 0xf2:
				hs->p_rep = c;
				pref |= 0x02;
				break;
			case 0xf0:
				hs->p_lock = c;
				pref |= 0x20;
				break;
			case 0x26: case 0x2e: case 0x36:
			case 0x3e: case 0x64: case 0x65:
				hs->p_seg = c;
				pref |= 0x40;
				break;
			case 0x66:
				hs->p_66 = c;
				pref |= 0x08;
				break;
			case 0x67:
				hs->p_67 = c;
				pref |= 0x10;
				break;
			default:
				goto pref_done;
			}
		}
	pref_done:
		hs->flags = (UINT32)pref << 23;
		if (!pref)
		{
			pref |= 0x01;
		}
		if ((c & 0xf0) == 0x40)
		{
			hs->flags |= 0x40000000;
			if ((hs->rex_w = (c & 0xf) >> 3) && (*p & 0xf8) == 0xb8)
			{
				op64++;
			}
			hs->rex_r = (c & 7) >> 2;
			hs->rex_x = (c & 3) >> 1;
			hs->rex_b = c & 1;
			if (((c = *p++) & 0xf0) == 0x40)
			{
				opcode = c;
				goto error_opcode;
			}
		}
		if ((hs->opcode = c) == 0x0f)
		{
			hs->opcode2 = c = *p++;
			ht += 0x4a;
		}
		else if (c >= 0xa0 && c <= 0xa3)
		{
			op64++;
			if (pref & 0x10)
			{
				pref |= 0x08;
			}
			else
			{
				pref &= ~0x08;
			}
		}
		opcode = c;
		cflags = ht[ht[opcode / 4] + (opcode % 4)];

		if (cflags == 0xff)
		{
		error_opcode:
			hs->flags |= 0x00001000 | 0x00002000;
			cflags = 0;
			if ((opcode & -3) == 0x24)
			{
				cflags++;
			}
		}

		x = 0;
		if (cflags & 0x80)
		{
			UINT16 t;
			t = *(UINT16*)(ht + (cflags & 0x7f));
			cflags = (UINT8)t;
			x = (UINT8)(t >> 8);
		}

		if (hs->opcode2)
		{
			ht = hde64_table + 0x13c;
			if (ht[ht[opcode / 4] + (opcode % 4)] & pref)
			{
				hs->flags |= 0x00001000 | 0x00002000;
			}
		}
		if (cflags & 0x01)
		{
			hs->flags |= 0x00000001;
			hs->modrm = c = *p++;
			hs->modrm_mod = m_mod = c >> 6;
			hs->modrm_rm = m_rm = c & 7;
			hs->modrm_reg = m_reg = (c & 0x3f) >> 3;
			if (x && ((x << m_reg) & 0x80))
			{
				hs->flags |= 0x00001000 | 0x00002000;
			}
			if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf)
			{
				UINT8 t = opcode - 0xd9;
				if (m_mod == 3)
				{
					ht = hde64_table + 0x104 + t * 8;
					t = ht[m_reg] << m_rm;
				}
				else
				{
					ht = hde64_table + 0xfd;
					t = ht[t] << m_reg;
				}
				if (t & 0x80)
				{
					hs->flags |= 0x00001000 | 0x00002000;
				}
			}
			if (pref & 0x20)
			{
				if (m_mod == 3)
				{
					hs->flags |= 0x00001000 | 0x00008000;
				}
				else
				{
					UINT8* table_end, op = opcode;
					if (hs->opcode2)
					{
						ht = hde64_table + 0x1c6;
						table_end = ht + 0x1d8 - 0x1c6;
					}
					else
					{
						ht = hde64_table + 0x1ae;
						table_end = ht + 0x1c6 - 0x1ae;
						op &= -2;
					}
					for (; ht != table_end; ht++)
					{
						if (*ht++ == op)
						{
							if (!((*ht << m_reg) & 0x80))
							{
								goto no_lock_error;
							}
							else
							{
								break;
							}
						}
					}
					hs->flags |= 0x00001000 | 0x00008000;
				no_lock_error:;
				}
			}

			if (hs->opcode2)
			{
				switch (opcode)
				{
				case 0x20: case 0x22:
					m_mod = 3;
					if (m_reg > 4 || m_reg == 1)
					{
						goto error_operand;
					}
					else
					{
						goto no_error_operand;
					}
				case 0x21: case 0x23:
					m_mod = 3;
					if (m_reg == 4 || m_reg == 5)
					{
						goto error_operand;
					}
					else
					{
						goto no_error_operand;
					}
				}
			}
			else
			{
				switch (opcode)
				{
				case 0x8c:
					if (m_reg > 5)
					{
						goto error_operand;
					}
					else
					{
						goto no_error_operand;
					}
				case 0x8e:
					if (m_reg == 1 || m_reg > 5)
					{
						goto error_operand;
					}
					else
					{
						goto no_error_operand;
					}
				}
			}
			if (m_mod == 3)
			{
				UINT8* table_end;
				if (hs->opcode2)
				{
					ht = hde64_table + 0x1e7;
					table_end = ht + sizeof(hde64_table) - 0x1e7;
				}
				else
				{
					ht = hde64_table + 0x1d8;
					table_end = ht + 0x1e7 - 0x1d8;
				}
				for (; ht != table_end; ht += 2)
				{
					if (*ht++ == opcode)
					{
						if (*ht++ & pref && !((*ht << m_reg) & 0x80))
						{
							goto error_operand;
						}
						else
						{
							break;
						}
					}
				}
				goto no_error_operand;
			}
			else if (hs->opcode2)
			{
				switch (opcode)
				{
				case 0x50: case 0xd7: case 0xf7:
					if (pref & (0x01 | 0x08))
					{
						goto error_operand;
					}
					break;
				case 0xd6:
					if (pref & (0x02 | 0x04))
					{
						goto error_operand;
					}
					break;
				case 0xc5:
					goto error_operand;
				}
				goto no_error_operand;
			}
			else
			{
				goto no_error_operand;
			}
		error_operand:
			hs->flags |= 0x00001000 | 0x00010000;
		no_error_operand:
			c = *p++;
			if (m_reg <= 1)
			{
				if (opcode == 0xf6)
				{
					cflags |= 0x02;
				}
				else if (opcode == 0xf7)
				{
					cflags |= 0x10;
				}
			}
			switch (m_mod)
			{
			case 0:
				if (pref & 0x10)
				{
					if (m_rm == 6)
					{
						disp_size = 2;
					}
				}
				else
					if (m_rm == 5)
					{
						disp_size = 4;
					}
				break;
			case 1:
				disp_size = 1;
				break;
			case 2:
				disp_size = 2;
				if (!(pref & 0x10))
				{
					disp_size <<= 1;
				}
			}
			if (m_mod != 3 && m_rm == 4)
			{
				hs->flags |= 0x00000002;
				p++;
				hs->sib = c;
				hs->sib_scale = c >> 6;
				hs->sib_index = (c & 0x3f) >> 3;
				if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1))
				{
					disp_size = 4;
				}
			}
			p--;
			switch (disp_size)
			{
			case 1:
				hs->flags |= 0x00000040;
				hs->disp.disp8 = *p;
				break;
			case 2:
				hs->flags |= 0x00000080;
				hs->disp.disp16 = *(UINT16*)p;
				break;
			case 4:
				hs->flags |= 0x00000100;
				hs->disp.disp32 = *(UINT32*)p;
			}
			p += disp_size;
		}
		else if (pref & 0x20)
		{
			hs->flags |= 0x00001000 | 0x00008000;
		}
		if (cflags & 0x10)
		{
			if (cflags & 0x40)
			{
				if (pref & 0x08)
				{
					hs->flags |= 0x00000008 | 0x00000200;
					hs->imm.imm16 = *(UINT16*)p;
					p += 2;
					goto disasm_done;
				}
				goto rel32_ok;
			}
			if (op64)
			{
				hs->flags |= 0x00000020;
				hs->imm.imm64 = *(UINT64*)p;
				p += 8;
			}
			else if (!(pref & 0x08))
			{
				hs->flags |= 0x00000010;
				hs->imm.imm32 = *(UINT32*)p;
				p += 4;
			}
			else
			{
				goto imm16_ok;
			}
		}
		if (cflags & 0x04)
		{
		imm16_ok:
			hs->flags |= 0x00000008;
			hs->imm.imm16 = *(UINT16*)p;
			p += 2;
		}
		if (cflags & 0x02)
		{
			hs->flags |= 0x00000004;
			hs->imm.imm8 = *p++;
		}
		if (cflags & 0x40)
		{
		rel32_ok:
			hs->flags |= 0x00000010 | 0x00000200;
			hs->imm.imm32 = *(UINT32*)p;
			p += 4;
		}
		else if (cflags & 0x20)
		{
			hs->flags |= 0x00000004 | 0x00000200;
			hs->imm.imm8 = *p++;
		}
	disasm_done:
		if ((hs->len = (UINT8)(p - (UINT8*)code)) > 15)
		{
			hs->flags |= 0x00001000 | 0x00004000;
			hs->len = 15;
		}
		return (unsigned int)hs->len;
	}
#pragma pack(pop)
#else
#pragma pack(push,1)
	typedef struct {
		UINT8 len;
		UINT8 p_rep;
		UINT8 p_lock;
		UINT8 p_seg;
		UINT8 p_66;
		UINT8 p_67;
		UINT8 opcode;
		UINT8 opcode2;
		UINT8 modrm;
		UINT8 modrm_mod;
		UINT8 modrm_reg;
		UINT8 modrm_rm;
		UINT8 sib;
		UINT8 sib_scale;
		UINT8 sib_index;
		UINT8 sib_base;
		union {
			UINT8 imm8;
			UINT16 imm16;
			UINT32 imm32;
		} imm;
		union {
			UINT8 disp8;
			UINT16 disp16;
			UINT32 disp32;
		} disp;
		UINT32 flags;
	} hde32s;
#pragma pack(pop)
	typedef hde32s HDE;
	static unsigned int hde_disasm(const void* code, HDE* hs)
	{
		unsigned char hde32_table[] = {
  0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,
  0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f,
  0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3,
  0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa,
  0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90,
  0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f,
  0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d,
  0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59,
  0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,
  0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0,
  0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01,
  0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11,
  0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8,
  0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca,
  0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff,
  0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03,
  0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,
  0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,
  0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f,
  0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a,
  0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,
  0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a,
  0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06,
  0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06,
  0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04,
  0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08,
  0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,
  0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,
  0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,
  0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,
  0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,
  0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,
  0xe7,0x08,0x00,0xf0,0x02,0x00
		};

		UINT8 x, c, * p = (UINT8*)code, cflags, opcode, pref = 0;
		UINT8* ht = hde32_table, m_mod, m_reg, m_rm, disp_size = 0;
		mh_memset((LPBYTE)hs, 0, sizeof(hde32s));
		for (x = 16; x; x--)
			switch (c = *p++)
			{
			case 0xf3:
				hs->p_rep = c;
				pref |= 0x04;
				break;
			case 0xf2:
				hs->p_rep = c;
				pref |= 0x02;
				break;
			case 0xf0:
				hs->p_lock = c;
				pref |= 0x20;
				break;
			case 0x26: case 0x2e: case 0x36:
			case 0x3e: case 0x64: case 0x65:
				hs->p_seg = c;
				pref |= 0x40;
				break;
			case 0x66:
				hs->p_66 = c;
				pref |= 0x08;
				break;
			case 0x67:
				hs->p_67 = c;
				pref |= 0x10;
				break;
			default:
				goto pref_done;
			}
	pref_done:
		hs->flags = (UINT32)pref << 23;
		if (!pref)
		{
			pref |= 0x01;
		}
		if ((hs->opcode = c) == 0x0f)
		{
			hs->opcode2 = c = *p++;
			ht += 0x4a;
		}
		else if (c >= 0xa0 && c <= 0xa3)
		{
			if (pref & 0x10)
			{
				pref |= 0x08;
			}
			else
			{
				pref &= ~0x08;
			}
		}
		opcode = c;
		cflags = ht[ht[opcode / 4] + (opcode % 4)];
		if (cflags == 0xff)
		{
			hs->flags |= 0x00001000 | 0x00002000;
			cflags = 0;
			if ((opcode & -3) == 0x24)
			{
				cflags++;
			}
		}
		x = 0;
		if (cflags & 0x80)
		{
			UINT16 t;
			t = *(UINT16*)(ht + (cflags & 0x7f));
			cflags = (UINT8)t;
			x = (UINT8)(t >> 8);
		}
		if (hs->opcode2)
		{
			ht = hde32_table + 0x130;
			if (ht[ht[opcode / 4] + (opcode % 4)] & pref)
			{
				hs->flags |= 0x00001000 | 0x00002000;
			}
		}

		if (cflags & 0x01)
		{
			hs->flags |= 0x00000001;
			hs->modrm = c = *p++;
			hs->modrm_mod = m_mod = c >> 6;
			hs->modrm_rm = m_rm = c & 7;
			hs->modrm_reg = m_reg = (c & 0x3f) >> 3;
			if (x && ((x << m_reg) & 0x80))
			{
				hs->flags |= 0x00001000 | 0x00002000;
			}
			if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf)
			{
				UINT8 t = opcode - 0xd9;
				if (m_mod == 3)
				{
					ht = hde32_table + 0xf8 + t * 8;
					t = ht[m_reg] << m_rm;
				}
				else
				{
					ht = hde32_table + 0xf1;
					t = ht[t] << m_reg;
				}
				if (t & 0x80)
				{
					hs->flags |= 0x00001000 | 0x00002000;
				}
			}

			if (pref & 0x20)
			{
				if (m_mod == 3)
				{
					hs->flags |= 0x00001000 | 0x00008000;
				}
				else
				{
					UINT8* table_end, op = opcode;
					if (hs->opcode2)
					{
						ht = hde32_table + 0x1b9;
						table_end = ht + 0x1cb - 0x1b9;
					}
					else
					{
						ht = hde32_table + 0x1a1;
						table_end = ht + 0x1b9 - 0x1a1;
						op &= -2;
					}
					for (; ht != table_end; ht++)
					{
						if (*ht++ == op)
						{
							if (!((*ht << m_reg) & 0x80))
							{
								goto no_lock_error;
							}
							else
							{
								break;
							}
						}
					}
					hs->flags |= 0x00001000 | 0x00008000;
				no_lock_error:
					;
				}
			}

			if (hs->opcode2)
			{
				switch (opcode)
				{
				case 0x20: case 0x22:
					m_mod = 3;
					if (m_reg > 4 || m_reg == 1)
					{
						goto error_operand;
					}
					else
					{
						goto no_error_operand;
					}
				case 0x21: case 0x23:
					m_mod = 3;
					if (m_reg == 4 || m_reg == 5)
					{
						goto error_operand;
					}
					else
					{
						goto no_error_operand;
					}
				}
			}
			else {
				switch (opcode)
				{
				case 0x8c:
					if (m_reg > 5)
					{
						goto error_operand;
					}
					else
					{
						goto no_error_operand;
					}
				case 0x8e:
					if (m_reg == 1 || m_reg > 5)
					{
						goto error_operand;
					}
					else
					{
						goto no_error_operand;
					}
				}
			}

			if (m_mod == 3) {
				UINT8* table_end;
				if (hs->opcode2)
				{
					ht = hde32_table + 0x1da;
					table_end = ht + sizeof(hde32_table) - 0x1da;
				}
				else
				{
					ht = hde32_table + 0x1cb;
					table_end = ht + 0x1da - 0x1cb;
				}
				for (; ht != table_end; ht += 2)
					if (*ht++ == opcode)
					{
						if (*ht++ & pref && !((*ht << m_reg) & 0x80))
						{
							goto error_operand;
						}
						else
						{
							break;
						}
					}
				goto no_error_operand;
			}
			else if (hs->opcode2)
			{
				switch (opcode)
				{
				case 0x50: case 0xd7: case 0xf7:
					if (pref & (0x01 | 0x08))
					{
						goto error_operand;
					}
					break;
				case 0xd6:
					if (pref & (0x02 | 0x04))
					{
						goto error_operand;
					}
					break;
				case 0xc5:
					goto error_operand;
				}
				goto no_error_operand;
			}
			else
			{
				goto no_error_operand;
			}
		error_operand:
			hs->flags |= 0x00001000 | 0x00010000;
		no_error_operand:
			c = *p++;
			if (m_reg <= 1)
			{
				if (opcode == 0xf6)
				{
					cflags |= 0x02;
				}
				else if (opcode == 0xf7)
				{
					cflags |= 0x10;
				}
			}

			switch (m_mod)
			{
			case 0:
				if (pref & 0x10)
				{
					if (m_rm == 6)
					{
						disp_size = 2;
					}
				}
				else
					if (m_rm == 5)
					{
						disp_size = 4;
					}
				break;
			case 1:
				disp_size = 1;
				break;
			case 2:
				disp_size = 2;
				if (!(pref & 0x10))
				{
					disp_size <<= 1;
				}
			}
			if (m_mod != 3 && m_rm == 4 && !(pref & 0x10))
			{
				hs->flags |= 0x00000002;
				p++;
				hs->sib = c;
				hs->sib_scale = c >> 6;
				hs->sib_index = (c & 0x3f) >> 3;
				if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1))
				{
					disp_size = 4;
				}
			}
			p--;
			switch (disp_size)
			{
			case 1:
				hs->flags |= 0x00000020;
				hs->disp.disp8 = *p;
				break;
			case 2:
				hs->flags |= 0x00000040;
				hs->disp.disp16 = *(UINT16*)p;
				break;
			case 4:
				hs->flags |= 0x00000080;
				hs->disp.disp32 = *(UINT32*)p;
			}
			p += disp_size;
		}
		else if (pref & 0x20)
		{
			hs->flags |= 0x00001000 | 0x00008000;
		}

		if (cflags & 0x10)
		{
			if (cflags & 0x40)
			{
				if (pref & 0x08)
				{
					hs->flags |= 0x00000008 | 0x00000100;
					hs->imm.imm16 = *(UINT16*)p;
					p += 2;
					goto disasm_done;
				}
				goto rel32_ok;
			}
			if (pref & 0x08)
			{
				hs->flags |= 0x00000008;
				hs->imm.imm16 = *(UINT16*)p;
				p += 2;
			}
			else
			{
				hs->flags |= 0x00000010;
				hs->imm.imm32 = *(UINT32*)p;
				p += 4;
			}
		}

		if (cflags & 0x04)
		{
			if (hs->flags & 0x00000010)
			{
				hs->flags |= 0x00000008;
				hs->disp.disp16 = *(UINT16*)p;
			}
			else if (hs->flags & 0x00000008)
			{
				hs->flags |= 0x00000800;
				hs->disp.disp16 = *(UINT16*)p;
			}
			else
			{
				hs->flags |= 0x00000008;
				hs->imm.imm16 = *(UINT16*)p;
			}
			p += 2;
		}
		if (cflags & 0x02)
		{
			hs->flags |= 0x00000004;
			hs->imm.imm8 = *p++;
		}

		if (cflags & 0x40)
		{
		rel32_ok:
			hs->flags |= 0x00000010 | 0x00000100;
			hs->imm.imm32 = *(UINT32*)p;
			p += 4;
		}
		else if (cflags & 0x20)
		{
			hs->flags |= 0x00000004 | 0x00000100;
			hs->imm.imm8 = *p++;
		}
	disasm_done:
		if ((hs->len = (UINT8)(p - (UINT8*)code)) > 15)
		{
			hs->flags |= 0x00001000 | 0x00004000;
			hs->len = 15;
		}
		return (unsigned int)hs->len;
	}
#endif
public:
	struct MH_HOOK_DATA
	{
		volatile LONG g_isLocked;
		HANDLE g_hHeap;
		PMEMORY_BLOCK g_pMemoryBlocks;
		_HOOKS g_hooks;
		unsigned int ErrorCode;
	};
	static int MH_ERROR_CODE(int code, bool is_set)
	{
		static unsigned int* ErrorCode = 0;
		if (ErrorCode)
		{
			unsigned int m_code = 0;
			if (is_set)
			{
				m_code = ::_InterlockedExchange(ErrorCode, code);
			}
			else
			{
				m_code = ::_InterlockedCompareExchange(ErrorCode, 0, 0);
			}
			return m_code;
		}
		MH_HOOK_DATA* ptr = MH_GET_DATA();
		ErrorCode = &ptr->ErrorCode;
		return MH_ERROR_CODE(code,is_set);
	}
	static MH_HOOK_DATA* MH_GET_DATA()
	{
		static MH_HOOK_DATA* data = 0;
		if (data)
		{
			return data;
		}
		bool is_ok;
		MH_HOOK_DATA* ptr = (MH_HOOK_DATA*)mod::env::get("minhook", &is_ok);
		if (is_ok)
		{
			data = ptr;
			return data;
		}
		ptr = new MH_HOOK_DATA;
		mh_memset(ptr, 0, sizeof(MH_HOOK_DATA));
		mod::env::set("minhook", &ptr);
		data = ptr;
		return data;
	}
	//static volatile LONG g_isLocked;
	//static HANDLE g_hHeap;
	//static PMEMORY_BLOCK g_pMemoryBlocks;
	//static _HOOKS g_hooks;
public:
	static void EnterSpinLock()
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		size_t spinCount = 0;
		while (InterlockedCompareExchange(&g->g_isLocked, TRUE, FALSE) != FALSE)
		{
			if (spinCount < 32)
			{
				Sleep(0);
			}
			else
			{
				Sleep(1);
			}
			spinCount++;
		}
	}
	static void LeaveSpinLock()
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		InterlockedExchange(&g->g_isLocked, FALSE);
	}
	static BOOL IsExecutableAddress(LPVOID pAddress)
	{
		MEMORY_BASIC_INFORMATION mi;
		VirtualQuery(pAddress, &mi, sizeof(mi));
		return (mi.State == MEM_COMMIT && (mi.Protect & PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY));
	}
	static UINT FindHookEntry(LPVOID pTarget)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		for (UINT i = 0; i < g->g_hooks.size; ++i)
		{
			if ((ULONG_PTR)pTarget == (ULONG_PTR)g->g_hooks.pItems[i].pTarget)
			{
				return i;
			}
		}

		return UINT_MAX;
	}
	static PHOOK_ENTRY AddHookEntry()
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		if (g->g_hooks.pItems == NULL)
		{
			g->g_hooks.capacity = 32;
			g->g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc(g->g_hHeap, 0, g->g_hooks.capacity * sizeof(HOOK_ENTRY));
			if (g->g_hooks.pItems == NULL)
			{
				return NULL;
			}
		}
		else if (g->g_hooks.size >= g->g_hooks.capacity)
		{
			PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc(g->g_hHeap, 0, g->g_hooks.pItems, (g->g_hooks.capacity * 2) * sizeof(HOOK_ENTRY));
			if (p == NULL)
			{
				return NULL;
			}
			g->g_hooks.capacity *= 2;
			g->g_hooks.pItems = p;
		}
		return &g->g_hooks.pItems[g->g_hooks.size++];
	}
	static void DeleteHookEntry(UINT pos)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		if (pos < g->g_hooks.size - 1)
		{
			g->g_hooks.pItems[pos] = g->g_hooks.pItems[g->g_hooks.size - 1];
		}
		g->g_hooks.size--;
		if (g->g_hooks.capacity / 2 >= 32 && g->g_hooks.capacity / 2 >= g->g_hooks.size)
		{
			PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc(g->g_hHeap, 0, g->g_hooks.pItems, (g->g_hooks.capacity / 2) * sizeof(HOOK_ENTRY));
			if (p == NULL)
			{
				return;
			}
			g->g_hooks.capacity /= 2;
			g->g_hooks.pItems = p;
		}
	}
	static DWORD_PTR FindOldIP(PHOOK_ENTRY pHook, DWORD_PTR ip)
	{
		if (pHook->patchAbove && ip == ((DWORD_PTR)pHook->pTarget - sizeof(JMP_REL)))
			return (DWORD_PTR)pHook->pTarget;

		for (UINT i = 0; i < pHook->nIP; ++i)
		{
			if (ip == ((DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i]))
			{
				return (DWORD_PTR)pHook->pTarget + pHook->oldIPs[i];
			}
		}
#if defined(_M_X64) || defined(__x86_64__)
		// Check relay function.
		if (ip == (DWORD_PTR)pHook->pDetour)
		{
			return (DWORD_PTR)pHook->pTarget;
		}
#endif
		return 0;
	}
	static DWORD_PTR FindNewIP(PHOOK_ENTRY pHook, DWORD_PTR ip)
	{
		for (UINT i = 0; i < pHook->nIP; ++i)
		{
			if (ip == ((DWORD_PTR)pHook->pTarget + pHook->oldIPs[i]))
			{
				return (DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i];
			}
		}
		return 0;
	}
	//-------------------------------------------------------------------------
	static void ProcessThreadIPs(HANDLE hThread, UINT pos, UINT action)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		CONTEXT c;
#if defined(_M_X64) || defined(__x86_64__)
		DWORD64* pIP = &c.Rip;
#else
		DWORD* pIP = &c.Eip;
#endif
		UINT count;
		c.ContextFlags = CONTEXT_CONTROL;
		if (!GetThreadContext(hThread, &c))
		{
			return;
		}
		if (pos == UINT_MAX)
		{
			pos = 0;
			count = g->g_hooks.size;
		}
		else
		{
			count = pos + 1;
		}
		for (; pos < count; ++pos)
		{
			PHOOK_ENTRY pHook = &g->g_hooks.pItems[pos];
			BOOL        enable;
			DWORD_PTR   ip;
			switch (action)
			{
			case 0:
				enable = FALSE;
				break;
			case 1:
				enable = TRUE;
				break;
			default:
				enable = pHook->queueEnable;
				break;
			}
			if (pHook->isEnabled == enable)
			{
				continue;
			}
			if (enable)
			{
				ip = FindNewIP(pHook, *pIP);
			}
			else
			{
				ip = FindOldIP(pHook, *pIP);
			}
			if (ip != 0)
			{
				*pIP = ip;
				SetThreadContext(hThread, &c);
			}
		}
	}
	//-------------------------------------------------------------------------
	static void EnumerateThreads(PFROZEN_THREADS pThreads)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
		if (hSnapshot != INVALID_HANDLE_VALUE)
		{
			THREADENTRY32 te;
			te.dwSize = sizeof(THREADENTRY32);
			if (Thread32First(hSnapshot, &te))
			{
				do
				{
					if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD)) && te.th32OwnerProcessID == GetCurrentProcessId() && te.th32ThreadID != GetCurrentThreadId())
					{
						if (pThreads->pItems == NULL)
						{
							pThreads->capacity = 128;
							pThreads->pItems = (LPDWORD)HeapAlloc(g->g_hHeap, 0, pThreads->capacity * sizeof(DWORD));
							if (pThreads->pItems == NULL)
							{
								break;
							}
						}
						else if (pThreads->size >= pThreads->capacity)
						{
							LPDWORD p = (LPDWORD)HeapReAlloc(g->g_hHeap, 0, pThreads->pItems, (pThreads->capacity * 2) * sizeof(DWORD));
							if (p == NULL)
							{
								break;
							}
							pThreads->capacity *= 2;
							pThreads->pItems = p;
						}
						pThreads->pItems[pThreads->size++] = te.th32ThreadID;
					}
					te.dwSize = sizeof(THREADENTRY32);
				} while (Thread32Next(hSnapshot, &te));
			}
			CloseHandle(hSnapshot);
		}
	}
	//-------------------------------------------------------------------------
	static void Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action)
	{
		pThreads->pItems = NULL;
		pThreads->capacity = 0;
		pThreads->size = 0;
		EnumerateThreads(pThreads);
		if (pThreads->pItems != NULL)
		{
			UINT i;
			for (i = 0; i < pThreads->size; ++i)
			{
				HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT, FALSE, pThreads->pItems[i]);
				if (hThread != NULL)
				{
					SuspendThread(hThread);
					ProcessThreadIPs(hThread, pos, action);
					CloseHandle(hThread);
				}
			}
		}
	}

	//-------------------------------------------------------------------------
	static VOID Unfreeze(PFROZEN_THREADS pThreads)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		if (pThreads->pItems != NULL)
		{
			for (UINT i = 0; i < pThreads->size; ++i)
			{
				HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT, FALSE, pThreads->pItems[i]);
				if (hThread != NULL)
				{
					ResumeThread(hThread);
					CloseHandle(hThread);
				}
			}
			HeapFree(g->g_hHeap, 0, pThreads->pItems);
		}
	}

	//-------------------------------------------------------------------------
	static MH_STATUS EnableHookLL(UINT pos, BOOL enable)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		PHOOK_ENTRY pHook = &g->g_hooks.pItems[pos];
		DWORD  oldProtect;
		SIZE_T patchSize = sizeof(JMP_REL);
		LPBYTE pPatchTarget = (LPBYTE)pHook->pTarget;

		if (pHook->patchAbove)
		{
			pPatchTarget -= sizeof(JMP_REL);
			patchSize += sizeof(JMP_REL_SHORT);
		}
		if (!VirtualProtect(pPatchTarget, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect))
		{
			return MH_STATUS::MH_ERROR_MEMORY_PROTECT;
		}
		if (enable)
		{
			PJMP_REL pJmp = (PJMP_REL)pPatchTarget;
			pJmp->opcode = 0xE9;
			pJmp->operand = (UINT32)((LPBYTE)pHook->pDetour - (pPatchTarget + sizeof(JMP_REL)));
			if (pHook->patchAbove)
			{
				PJMP_REL_SHORT pShortJmp = (PJMP_REL_SHORT)pHook->pTarget;
				pShortJmp->opcode = 0xEB;
				pShortJmp->operand = (UINT8)(0 - (sizeof(JMP_REL_SHORT) + sizeof(JMP_REL)));
			}
		}
		else
		{
			if (pHook->patchAbove)
			{
				mh_memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
			}
			else
			{
				mh_memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL));
			}
		}
		VirtualProtect(pPatchTarget, patchSize, oldProtect, &oldProtect);
		// Just-in-case measure.
		FlushInstructionCache(GetCurrentProcess(), pPatchTarget, patchSize);
		pHook->isEnabled = enable;
		pHook->queueEnable = enable;
		return MH_STATUS::MH_OK;
	}
	static MH_STATUS EnableAllHooksLL(BOOL enable)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		MH_STATUS status = MH_STATUS::MH_OK;
		UINT i, first = UINT_MAX;
		for (i = 0; i < g->g_hooks.size; ++i)
		{
			if (g->g_hooks.pItems[i].isEnabled != enable)
			{
				first = i;
				break;
			}
		}
		if (first != UINT_MAX)
		{
			FROZEN_THREADS threads;
			Freeze(&threads, UINT_MAX, enable ? 1 : 0);
			for (i = first; i < g->g_hooks.size; ++i)
			{
				if (g->g_hooks.pItems[i].isEnabled != enable)
				{
					status = EnableHookLL(i, enable);
					if (status != MH_STATUS::MH_OK)
					{
						break;
					}
				}
			}

			Unfreeze(&threads);
		}

		return status;
	}
	static void InitializeBuffer()
	{
		// Nothing to do for now.
	}
	static void UninitializeBuffer()
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		PMEMORY_BLOCK pBlock = g->g_pMemoryBlocks;
		g->g_pMemoryBlocks = NULL;
		while (pBlock)
		{
			PMEMORY_BLOCK pNext = pBlock->pNext;
			VirtualFree(pBlock, 0, MEM_RELEASE);
			pBlock = pNext;
		}
	}
#if defined(_M_X64) || defined(__x86_64__)
	static LPVOID FindPrevFreeRegion(LPVOID pAddress, LPVOID pMinAddr, DWORD dwAllocationGranularity)
	{
		ULONG_PTR tryAddr = (ULONG_PTR)pAddress;
		tryAddr -= tryAddr % dwAllocationGranularity;
		tryAddr -= dwAllocationGranularity;
		while (tryAddr >= (ULONG_PTR)pMinAddr)
		{
			MEMORY_BASIC_INFORMATION mbi;
			if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0)
			{
				break;
			}
			if (mbi.State == MEM_FREE)
			{
				return (LPVOID)tryAddr;
			}
			if ((ULONG_PTR)mbi.AllocationBase < dwAllocationGranularity)
			{
				break;
			}
			tryAddr = (ULONG_PTR)mbi.AllocationBase - dwAllocationGranularity;
		}
		return NULL;
	}
#endif

	//-------------------------------------------------------------------------
#if defined(_M_X64) || defined(__x86_64__)
	static LPVOID FindNextFreeRegion(LPVOID pAddress, LPVOID pMaxAddr, DWORD dwAllocationGranularity)
	{
		ULONG_PTR tryAddr = (ULONG_PTR)pAddress;
		tryAddr -= tryAddr % dwAllocationGranularity;
		tryAddr += dwAllocationGranularity;
		while (tryAddr <= (ULONG_PTR)pMaxAddr)
		{
			MEMORY_BASIC_INFORMATION mbi;
			if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0)
			{
				break;
			}
			if (mbi.State == MEM_FREE)
			{
				return (LPVOID)tryAddr;
			}
			tryAddr = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize;
			tryAddr += dwAllocationGranularity - 1;
			tryAddr -= tryAddr % dwAllocationGranularity;
		}
		return NULL;
	}
#endif
	static PMEMORY_BLOCK GetMemoryBlock(LPVOID pOrigin)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		PMEMORY_BLOCK pBlock;
#if defined(_M_X64) || defined(__x86_64__)
		ULONG_PTR minAddr;
		ULONG_PTR maxAddr;
		SYSTEM_INFO si;
		GetSystemInfo(&si);
		minAddr = (ULONG_PTR)si.lpMinimumApplicationAddress;
		maxAddr = (ULONG_PTR)si.lpMaximumApplicationAddress;
		if ((ULONG_PTR)pOrigin > 0x40000000 && minAddr < (ULONG_PTR)pOrigin - 0x40000000)
		{
			minAddr = (ULONG_PTR)pOrigin - 0x40000000;
		}
		if (maxAddr > (ULONG_PTR)pOrigin + 0x40000000)
		{
			maxAddr = (ULONG_PTR)pOrigin + 0x40000000;
		}
		maxAddr -= 0x1000 - 1;
#endif
		for (pBlock = g->g_pMemoryBlocks; pBlock != NULL; pBlock = pBlock->pNext)
		{
#if defined(_M_X64) || defined(__x86_64__)
			if ((ULONG_PTR)pBlock < minAddr || (ULONG_PTR)pBlock >= maxAddr)
			{
				continue;
			}
#endif
			if (pBlock->pFree != NULL)
			{
				return pBlock;
			}
		}

#if defined(_M_X64) || defined(__x86_64__)
		{
			LPVOID pAlloc = pOrigin;
			while ((ULONG_PTR)pAlloc >= minAddr)
			{
				pAlloc = FindPrevFreeRegion(pAlloc, (LPVOID)minAddr, si.dwAllocationGranularity);
				if (pAlloc == NULL)
				{
					break;
				}
				pBlock = (PMEMORY_BLOCK)VirtualAlloc(pAlloc, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
				if (pBlock != NULL)
				{
					break;
				}
			}
		}
		if (pBlock == NULL)
		{
			LPVOID pAlloc = pOrigin;
			while ((ULONG_PTR)pAlloc <= maxAddr)
			{
				pAlloc = FindNextFreeRegion(pAlloc, (LPVOID)maxAddr, si.dwAllocationGranularity);
				if (pAlloc == NULL)
				{
					break;
				}
				pBlock = (PMEMORY_BLOCK)VirtualAlloc(pAlloc, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
				if (pBlock != NULL)
				{
					break;
				}
			}
		}
#else
		// In x86 mode, a memory block can be placed anywhere.
		pBlock = (PMEMORY_BLOCK)VirtualAlloc(
			NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
#endif

		if (pBlock != NULL)
		{
			// Build a linked list of all the slots.
			PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBlock + 1;
			pBlock->pFree = NULL;
			pBlock->usedCount = 0;
#if defined(_M_X64) || defined(__x86_64__)
			do
			{
				pSlot->pNext = pBlock->pFree;
				pBlock->pFree = pSlot;
				pSlot++;
			} while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= 0x1000 - 64);
#else
			do
			{
				pSlot->pNext = pBlock->pFree;
				pBlock->pFree = pSlot;
				pSlot++;
			} while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= 0x1000 - 32);
#endif
			pBlock->pNext = g->g_pMemoryBlocks;
			g->g_pMemoryBlocks = pBlock;
		}

		return pBlock;
	}
	//-------------------------------------------------------------------------
	static LPVOID AllocateBuffer(LPVOID pOrigin)
	{
		PMEMORY_SLOT  pSlot;
		PMEMORY_BLOCK pBlock = GetMemoryBlock(pOrigin);
		if (pBlock == NULL)
		{
			return NULL;
		}
		pSlot = pBlock->pFree;
		pBlock->pFree = pSlot->pNext;
		pBlock->usedCount++;
#ifdef _DEBUG
		mh_memset(pSlot, 0xCC, sizeof(MEMORY_SLOT));
#endif
		return pSlot;
	}
	static void FreeBuffer(LPVOID pBuffer)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		PMEMORY_BLOCK pBlock = g->g_pMemoryBlocks;
		PMEMORY_BLOCK pPrev = NULL;
		ULONG_PTR pTargetBlock = ((ULONG_PTR)pBuffer / 0x1000) * 0x1000;
		while (pBlock != NULL)
		{
			if ((ULONG_PTR)pBlock == pTargetBlock)
			{
				PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBuffer;
#ifdef _DEBUG
				// Clear the released slot for debugging.
				mh_memset(pSlot, 0x00, sizeof(*pSlot));
#endif
				pSlot->pNext = pBlock->pFree;
				pBlock->pFree = pSlot;
				pBlock->usedCount--;
				if (pBlock->usedCount == 0)
				{
					if (pPrev)
					{
						pPrev->pNext = pBlock->pNext;
					}
					else
					{
						g->g_pMemoryBlocks = pBlock->pNext;
					}
					VirtualFree(pBlock, 0, MEM_RELEASE);
				}

				break;
			}
			pPrev = pBlock;
			pBlock = pBlock->pNext;
		}
	}

	//-------------------------------------------------------------------------
	static BOOL IsCodePadding(LPBYTE pInst, UINT size)
	{
		if (pInst[0] != 0x00 && pInst[0] != 0x90 && pInst[0] != 0xCC)
		{
			return FALSE;
		}
		for (UINT i = 1; i < size; ++i)
		{
			if (pInst[i] != pInst[0])
			{
				return FALSE;
			}
		}
		return TRUE;
	}
	//-------------------------------------------------------------------------
	static BOOL CreateTrampolineFunction(PTRAMPOLINE ct)
	{
#if defined(_M_X64) || defined(__x86_64__)
		CALL_ABS call = {
			0xFF, 0x15, 0x00000002, // FF15 00000002: CALL [RIP+8]
			0xEB, 0x08,             // EB 08:         JMP +10
			0x0000000000000000ULL   // Absolute destination address
		};
		JMP_ABS jmp = {
			0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
			0x0000000000000000ULL   // Absolute destination address
		};
		JCC_ABS jcc = {
			0x70, 0x0E,             // 7* 0E:         J** +16
			0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
			0x0000000000000000ULL   // Absolute destination address
		};
#else
		CALL_REL call = {
			0xE8,                   // E8 xxxxxxxx: CALL +5+xxxxxxxx
			0x00000000              // Relative destination address
		};
		JMP_REL jmp = {
			0xE9,                   // E9 xxxxxxxx: JMP +5+xxxxxxxx
			0x00000000              // Relative destination address
		};
		JCC_REL jcc = {
			0x0F, 0x80,             // 0F8* xxxxxxxx: J** +6+xxxxxxxx
			0x00000000              // Relative destination address
		};
#endif
		UINT8     oldPos = 0;
		UINT8     newPos = 0;
		ULONG_PTR jmpDest = 0;     // Destination address of an internal jump.
		BOOL      finished = FALSE; // Is the function completed?
#if defined(_M_X64) || defined(__x86_64__)
		UINT8     instBuf[16];
#endif
		ct->patchAbove = FALSE;
		ct->nIP = 0;
		do
		{
			HDE       hs;
			UINT      copySize;
			LPVOID    pCopySrc;
			ULONG_PTR pOldInst = (ULONG_PTR)ct->pTarget + oldPos;
			ULONG_PTR pNewInst = (ULONG_PTR)ct->pTrampoline + newPos;
			copySize = hde_disasm((LPVOID)pOldInst, &hs);
			if (hs.flags & 0x00001000)
			{
				return FALSE;
			}
			pCopySrc = (LPVOID)pOldInst;
			if (oldPos >= sizeof(JMP_REL))
			{
				// The trampoline function is long enough.
				// Complete the function with the jump to the target function.
#if defined(_M_X64) || defined(__x86_64__)
				jmp.address = pOldInst;
#else
				jmp.operand = (UINT32)(pOldInst - (pNewInst + sizeof(jmp)));
#endif
				pCopySrc = &jmp;
				copySize = sizeof(jmp);
				finished = TRUE;
			}
#if defined(_M_X64) || defined(__x86_64__)
			else if ((hs.modrm & 0xC7) == 0x05)
			{
				PUINT32 pRelAddr;
				mh_memcpy(instBuf, (LPBYTE)pOldInst, copySize);
				pCopySrc = instBuf;
				pRelAddr = (PUINT32)(instBuf + hs.len - ((hs.flags & 0x3C) >> 2) - 4);
				*pRelAddr = (UINT32)((pOldInst + hs.len + (INT32)hs.disp.disp32) - (pNewInst + hs.len));
				if (hs.opcode == 0xFF && hs.modrm_reg == 4)
				{
					finished = TRUE;
				}
			}
#endif
			else if (hs.opcode == 0xE8)
			{
				ULONG_PTR dest = pOldInst + hs.len + (INT32)hs.imm.imm32;
#if defined(_M_X64) || defined(__x86_64__)
				call.address = dest;
#else
				call.operand = (UINT32)(dest - (pNewInst + sizeof(call)));
#endif
				pCopySrc = &call;
				copySize = sizeof(call);
			}
			else if ((hs.opcode & 0xFD) == 0xE9)
			{
				ULONG_PTR dest = pOldInst + hs.len;
				if (hs.opcode == 0xEB) // isShort jmp
				{
					dest += (INT8)hs.imm.imm8;
				}
				else
				{
					dest += (INT32)hs.imm.imm32;
				}
				if ((ULONG_PTR)ct->pTarget <= dest && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL)))
				{
					if (jmpDest < dest)
					{
						jmpDest = dest;
					}
				}
				else
				{
#if defined(_M_X64) || defined(__x86_64__)
					jmp.address = dest;
#else
					jmp.operand = (UINT32)(dest - (pNewInst + sizeof(jmp)));
#endif
					pCopySrc = &jmp;
					copySize = sizeof(jmp);
					finished = (pOldInst >= jmpDest);
				}
			}
			else if ((hs.opcode & 0xF0) == 0x70 || (hs.opcode & 0xFC) == 0xE0 || (hs.opcode2 & 0xF0) == 0x80)
			{
				ULONG_PTR dest = pOldInst + hs.len;
				if ((hs.opcode & 0xF0) == 0x70 || (hs.opcode & 0xFC) == 0xE0)  // LOOPNZ/LOOPZ/LOOP/JECXZ
				{
					dest += (INT8)hs.imm.imm8;
				}
				else
				{
					dest += (INT32)hs.imm.imm32;
				}
				if ((ULONG_PTR)ct->pTarget <= dest && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL)))
				{
					if (jmpDest < dest)
					{
						jmpDest = dest;
					}
				}
				else if ((hs.opcode & 0xFC) == 0xE0)
				{
					return FALSE;
				}
				else
				{
					UINT8 cond = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F);
#if defined(_M_X64) || defined(__x86_64__)
					// Invert the condition in x64 mode to simplify the conditional jump logic.
					jcc.opcode = 0x71 ^ cond;
					jcc.address = dest;
#else
					jcc.opcode1 = 0x80 | cond;
					jcc.operand = (UINT32)(dest - (pNewInst + sizeof(jcc)));
#endif
					pCopySrc = &jcc;
					copySize = sizeof(jcc);
				}
			}
			else if ((hs.opcode & 0xFE) == 0xC2)
			{
				finished = (pOldInst >= jmpDest);
			}
			if (pOldInst < jmpDest && copySize != hs.len)
			{
				return FALSE;
			}
			if ((newPos + copySize) > sizeof(_MEMORY_SLOT::buffer))
			{
				return FALSE;
			}
			if (ct->nIP >= ARRAYSIZE(ct->oldIPs))
			{
				return FALSE;
			}
			ct->oldIPs[ct->nIP] = oldPos;
			ct->newIPs[ct->nIP] = newPos;
			ct->nIP++;
			mh_memcpy((LPBYTE)ct->pTrampoline + newPos, pCopySrc, copySize);
			newPos += copySize;
			oldPos += hs.len;
		} while (!finished);
		if (oldPos < sizeof(JMP_REL) && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL) - oldPos))
		{
			if (oldPos < sizeof(JMP_REL_SHORT) && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL_SHORT) - oldPos))
			{
				return FALSE;
			}
			if (!IsExecutableAddress((LPBYTE)ct->pTarget - sizeof(JMP_REL)))
			{
				return FALSE;
			}
			if (!IsCodePadding((LPBYTE)ct->pTarget - sizeof(JMP_REL), sizeof(JMP_REL)))
			{
				return FALSE;
			}
			ct->patchAbove = TRUE;
		}
#if defined(_M_X64) || defined(__x86_64__)
		// Create a relay function.
		jmp.address = (ULONG_PTR)ct->pDetour;
		ct->pRelay = (LPBYTE)ct->pTrampoline + newPos;
		mh_memcpy(ct->pRelay, &jmp, sizeof(jmp));
#endif
		return TRUE;
	}

public:
	static MH_STATUS __stdcall MH_Initialize()
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		MH_STATUS status = MH_STATUS::MH_OK;
		if (!g)
		{
			status = MH_ERROR_MEMORY_ALLOC;
			return status;
		}
		if (g->g_hHeap)
		{
			return status;
		}
		EnterSpinLock();
		if (g->g_hHeap == NULL)
		{
			g->g_hHeap = HeapCreate(0, 0, 0);
			if (g->g_hHeap != NULL)
			{
				InitializeBuffer();
			}
			else
			{
				status = MH_ERROR_MEMORY_ALLOC;
			}
		}
		else
		{
			status = MH_ERROR_ALREADY_INITIALIZED;
		}
		LeaveSpinLock();
		return status;
	}
	static MH_STATUS __stdcall MH_Uninitialize()
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		MH_STATUS status = MH_STATUS::MH_OK;
		EnterSpinLock();
		if (g->g_hHeap != NULL)
		{
			status = EnableAllHooksLL(FALSE);
			if (status == MH_STATUS::MH_OK)
			{
				UninitializeBuffer();
				HeapFree(g->g_hHeap, 0, g->g_hooks.pItems);
				HeapDestroy(g->g_hHeap);
				g->g_hHeap = NULL;
				g->g_hooks.pItems = NULL;
				g->g_hooks.capacity = 0;
				g->g_hooks.size = 0;
			}
		}
		else
		{
			status = MH_ERROR_NOT_INITIALIZED;
		}
		LeaveSpinLock();
		return status;
	}
	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID* ppOriginal)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		MH_STATUS status = MH_STATUS::MH_OK;
		EnterSpinLock();
		if (g->g_hHeap != NULL)
		{
			if (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour))
			{
				UINT pos = FindHookEntry(pTarget);
				if (pos == UINT_MAX)
				{
					LPVOID pBuffer = AllocateBuffer(pTarget);
					if (pBuffer != NULL)
					{
						TRAMPOLINE ct;

						ct.pTarget = pTarget;
						ct.pDetour = pDetour;
						ct.pTrampoline = pBuffer;
						if (CreateTrampolineFunction(&ct))
						{
							PHOOK_ENTRY pHook = AddHookEntry();
							if (pHook != NULL)
							{
								pHook->pTarget = ct.pTarget;
#if defined(_M_X64) || defined(__x86_64__)
								pHook->pDetour = ct.pRelay;
#else
								pHook->pDetour = ct.pDetour;
#endif
								pHook->pTrampoline = ct.pTrampoline;
								pHook->patchAbove = ct.patchAbove;
								pHook->isEnabled = FALSE;
								pHook->queueEnable = FALSE;
								pHook->nIP = ct.nIP;
								mh_memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs));
								mh_memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs));
								if (ct.patchAbove)
								{
									mh_memcpy(pHook->backup,(LPBYTE)pTarget - sizeof(JMP_REL),sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
								}
								else
								{
									mh_memcpy(pHook->backup, pTarget, sizeof(JMP_REL));
								}
								if (ppOriginal != NULL)
								{
									*ppOriginal = pHook->pTrampoline;
								}
							}
							else
							{
								status = MH_STATUS::MH_ERROR_MEMORY_ALLOC;
							}
						}
						else
						{
							status = MH_STATUS::MH_ERROR_UNSUPPORTED_FUNCTION;
						}
						if (status != MH_STATUS::MH_OK)
						{
							FreeBuffer(pBuffer);
						}
					}
					else
					{
						status = MH_STATUS::MH_ERROR_MEMORY_ALLOC;
					}
				}
				else
				{
					status = MH_STATUS::MH_ERROR_ALREADY_CREATED;
				}
			}
			else
			{
				status = MH_STATUS::MH_ERROR_NOT_EXECUTABLE;
			}
		}
		else
		{
			status = MH_STATUS::MH_ERROR_NOT_INITIALIZED;
		}

		LeaveSpinLock();

		return status;
	}

	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_RemoveHook(LPVOID pTarget)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		MH_STATUS status = MH_STATUS::MH_OK;

		EnterSpinLock();

		if (g->g_hHeap != NULL)
		{
			UINT pos = FindHookEntry(pTarget);
			if (pos != UINT_MAX)
			{
				if (g->g_hooks.pItems[pos].isEnabled)
				{
					FROZEN_THREADS threads;
					Freeze(&threads, pos, 0);

					status = EnableHookLL(pos, FALSE);

					Unfreeze(&threads);
				}

				if (status == MH_STATUS::MH_OK)
				{
					FreeBuffer(g->g_hooks.pItems[pos].pTrampoline);
					DeleteHookEntry(pos);
				}
			}
			else
			{
				status = MH_STATUS::MH_ERROR_NOT_CREATED;
			}
		}
		else
		{
			status = MH_STATUS::MH_ERROR_NOT_INITIALIZED;
		}

		LeaveSpinLock();

		return status;
	}

	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall EnableHook(LPVOID pTarget, BOOL enable)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		MH_STATUS status = MH_STATUS::MH_OK;

		EnterSpinLock();

		if (g->g_hHeap != NULL)
		{
			if (pTarget == NULL)
			{
				status = EnableAllHooksLL(enable);
			}
			else
			{
				FROZEN_THREADS threads;
				UINT pos = FindHookEntry(pTarget);
				if (pos != UINT_MAX)
				{
					if (g->g_hooks.pItems[pos].isEnabled != enable)
					{
						Freeze(&threads, pos, 1);
						status = EnableHookLL(pos, enable);
						Unfreeze(&threads);
					}
					else
					{
						status = enable ? MH_STATUS::MH_ERROR_ENABLED : MH_STATUS::MH_ERROR_DISABLED;
					}
				}
				else
				{
					status = MH_STATUS::MH_ERROR_NOT_CREATED;
				}
			}
		}
		else
		{
			status = MH_STATUS::MH_ERROR_NOT_INITIALIZED;
		}

		LeaveSpinLock();

		return status;
	}

	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_EnableHook(LPVOID pTarget)
	{
		return EnableHook(pTarget, TRUE);
	}

	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_DisableHook(LPVOID pTarget)
	{
		return EnableHook(pTarget, FALSE);
	}

	//-------------------------------------------------------------------------
	static MH_STATUS QueueHook(LPVOID pTarget, BOOL queueEnable)
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		MH_STATUS status = MH_STATUS::MH_OK;
		EnterSpinLock();
		if (g->g_hHeap != NULL)
		{
			if (pTarget == NULL)
			{
				UINT i;
				for (i = 0; i < g->g_hooks.size; ++i)
				{
					g->g_hooks.pItems[i].queueEnable = queueEnable;
				}
			}
			else
			{
				UINT pos = FindHookEntry(pTarget);
				if (pos != UINT_MAX)
				{
					g->g_hooks.pItems[pos].queueEnable = queueEnable;
				}
				else
				{
					status = MH_STATUS::MH_ERROR_NOT_CREATED;
				}
			}
		}
		else
		{
			status = MH_STATUS::MH_ERROR_NOT_INITIALIZED;
		}
		LeaveSpinLock();
		return status;
	}

	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_QueueEnableHook(LPVOID pTarget)
	{
		return QueueHook(pTarget, TRUE);
	}

	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_QueueDisableHook(LPVOID pTarget)
	{
		return QueueHook(pTarget, FALSE);
	}

	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_ApplyQueued()
	{
		MH_HOOK_DATA* g = MH_GET_DATA();
		MH_STATUS status = MH_STATUS::MH_OK;
		UINT i, first = UINT_MAX;
		EnterSpinLock();
		if (g->g_hHeap != NULL)
		{
			for (i = 0; i < g->g_hooks.size; ++i)
			{
				if (g->g_hooks.pItems[i].isEnabled != g->g_hooks.pItems[i].queueEnable)
				{
					first = i;
					break;
				}
			}
			if (first != UINT_MAX)
			{
				FROZEN_THREADS threads;
				Freeze(&threads, UINT_MAX, 2);
				for (i = first; i <g->g_hooks.size; ++i)
				{
					PHOOK_ENTRY pHook = &g->g_hooks.pItems[i];
					if (pHook->isEnabled != pHook->queueEnable)
					{
						status = EnableHookLL(i, pHook->queueEnable);
						if (status != MH_STATUS::MH_OK)
						{
							break;
						}
					}
				}
				Unfreeze(&threads);
			}
		}
		else
		{
			status = MH_STATUS::MH_ERROR_NOT_INITIALIZED;
		}
		LeaveSpinLock();
		return status;
	}

	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_CreateHookApiExW(LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal, LPVOID* ppTarget)
	{
		HMODULE hModule;
		LPVOID  pTarget;
		hModule = GetModuleHandleW(pszModule);
		if (hModule == NULL)
		{
			return MH_STATUS::MH_ERROR_MODULE_NOT_FOUND;
		}
		pTarget = (LPVOID)GetProcAddress(hModule, pszProcName);
		if (pTarget == NULL)
		{
			return MH_STATUS::MH_ERROR_FUNCTION_NOT_FOUND;
		}
		if (ppTarget != NULL)
		{
			*ppTarget = pTarget;
		}
		return MH_CreateHook(pTarget, pDetour, ppOriginal);
	}
	static MH_STATUS __stdcall MH_CreateHookApiExA(LPCSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal, LPVOID* ppTarget)
	{
		HMODULE hModule;
		LPVOID  pTarget;
		hModule = GetModuleHandleA(pszModule);
		if (hModule == NULL)
		{
			return MH_STATUS::MH_ERROR_MODULE_NOT_FOUND;
		}
		pTarget = (LPVOID)GetProcAddress(hModule, pszProcName);
		if (pTarget == NULL)
		{
			return MH_STATUS::MH_ERROR_FUNCTION_NOT_FOUND;
		}
		if (ppTarget != NULL)
		{
			*ppTarget = pTarget;
		}
		return MH_CreateHook(pTarget, pDetour, ppOriginal);
	}
	//-------------------------------------------------------------------------
	static MH_STATUS __stdcall MH_CreateHookApiW(LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal)
	{
		return MH_CreateHookApiExW(pszModule, pszProcName, pDetour, ppOriginal, NULL);
	}
	static MH_STATUS __stdcall MH_CreateHookApiA(LPCSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal)
	{
		return MH_CreateHookApiExA(pszModule, pszProcName, pDetour, ppOriginal, NULL);
	}

public:
	__TEMP_MINHOOK() {}
	~__TEMP_MINHOOK() {}
};
#endif
#endif